#include <iostream>
#include <stdlib.h>
#include <random>
#include <cmath>

#include "gtest/gtest.h"
#include "kpextSyncMisc.h"

using namespace std;
using  chronoms =  std::chrono::milliseconds;

void msleep(int ms){
    std::this_thread::sleep_for(
                chronoms(ms)
                );
}

TEST(SemaphoreQueT, acquireAndRelease){
    SemaphoreQueT<int> *sem = new SemaphoreQueT<int>();
    std::vector<int> inputs{0,1,2,3,4,5,5,6,7,8,9};
    std::vector<int> result;
    auto work = [&](SemaphoreQueT<int> * sem) -> int{
        while (1) {
            std::cout << __FILE__ << __LINE__
                      << " sem->acquire" << endl;
            int ret = sem->acquire(-1);
            msleep(10);
            if(ret >= 0){
                result.push_back(ret);
            }else{
                return -1;
            }
        }
//        return -1;
    };

    std::future <int> fut = std::async(
                std::launch::async, // async立即创建线程， deferred 在future wait时创建
                work,
                sem);

    msleep(50);
    for(auto item : inputs){
        sem->release(item);
    }

    while (sem->available() > 0) {
        msleep(50);
    }

    sem->setIsWakeupToQuit(true);
    sem->wakeupAll();

    fut.wait();
    EXPECT_EQ(result, inputs);
    EXPECT_EQ(-1, fut.get());
}

TEST(SemWrapNtoOneT, IO_SYNC){
    uint32_t wrapN = 4;
    uint32_t threadCnt = 4;

    auto sem = std::make_shared<SemWrapNtoOneT<float, float>>(wrapN);
    auto allThreadReg = std::make_shared<std::promise<void>>();

    auto work = [&, allThreadReg](SemWrapNtoOneT<float, float> * sem) -> int{
        if(!sem->waitAllThreadsRegistered(1000)){
            throw std::runtime_error("Wait Threads register timeout !");
        }

        float old = 0;
        float avg = 0;
        uint32_t cyccnt = 0;
        while (1) {
            float getin = 0;
            if(sem->acquireInput(&getin , 50)){
                std::stringstream ss;
                ss<<"Thread("
                 <<std::this_thread::get_id()
                <<") acquire input : "
                <<getin
                << std::endl;
                std::cout << ss.str();
                {
                    std::random_device r;
                    std::default_random_engine e1(r());
                    std::uniform_int_distribution<int> uniform_dist(1, 15);
                    int ttt = uniform_dist(e1);
                    msleep(ttt);
                }
                avg = avg + (getin - old);
                cyccnt++;
                if(cyccnt >= wrapN){
                    avg = avg / static_cast<float>(wrapN);
                    sem->pushOutput(avg);
                    avg = 0;
                    old = 0;
                    cyccnt = 0;
                }else{
                    old = getin;
                }
            }else{
                if(sem->getIsWakeupToQuit()){
                    return -1;
                }
            }
        }
        //        return -1;
    };


    typedef std::packaged_task<int(SemWrapNtoOneT<float, float> *)> TestTask;

    std::vector<std::shared_ptr<TestTask>> tasks;
    std::vector<std::shared_ptr<std::thread>> threads;

    for (uint32_t i = 0; i < threadCnt; ++i) {
        tasks.push_back(
                    std::make_shared<TestTask>(work)
                    );
        threads.push_back(
                    std::make_shared<std::thread>(std::ref(*(tasks.back().get())), sem.get())
                    );
        sem->registerThreadID(
                    threads.back().get()->get_id()
                    );
    }

    sem->setAllThreadsRegistered();
    for (uint32_t i = 0; i < 100; ++i) {
        for (uint32_t j = 0; j < wrapN; ++j) {
            float f = static_cast<float>((i*100 + j) * wrapN);
            sem->pushInput(f);
        }
    }

    float res = 0;
    std::vector<float> outputs;
    while (sem->acquireOutput(&res, 500)) {
        outputs.push_back(res);
    }

    sem->setIsWakeupToQuit(true);
    sem->wakeupAll();
    for (auto & t : threads) {
        t->join();
    }

    for (auto & t : tasks) {
        auto fu = t->get_future();
        EXPECT_EQ(-1, fu.get());
    }

    std::cout << "Outputs : {" << std::endl;
    for (auto & o : outputs) {
        std::cout << o << ", ";
    }
    std::cout << std::endl << "}" << std::endl;

    int count = static_cast<int>(outputs.size());
    for (int i = 1; i < count; ++i) {
        EXPECT_EQ(static_cast<float>(100), outputs[i] - outputs[i - 1]);
    }

}

GTEST_API_ int main(int argc, char **argv) {
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
